﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;

namespace Apewer.Web
{

    /// <summary></summary>
    public sealed class MiniServer
    {

        /// <summary></summary>
        public MiniServer()
        {
            SynchronousIO = true;
            MaxRequest = 1024 * 1024 * 1024;
        }

        #region configuration

        /// <summary>上下文处理程序。</summary>
        /// <remarks>默认值：未指定</remarks>
        public Action<MiniContext> Handler { get; set; }

        /// <summary>捕获处理请求的异常。</summary>
        /// <remarks>默认值：未指定</remarks>
        public Action<Exception> CatchException { get; set; }

        /// <summary>获取已指定的本地终结点。</summary>
        public IPEndPoint IPEndPoint { get; private set; }

        /// <summary>获取已监听的本地端口。</summary>
        public int Port { get => GetPort(); }

        /// <summary>入站超时毫秒数。指定 0 或 -1 时为无限大。</summary>
        /// <remarks>默认值：0</remarks>
        public int InboundTimeout { get; set; }

        /// <summary>出站超时毫秒数。指定 0 或 -1 时为无限大。</summary>
        /// <remarks>默认值：0</remarks>
        public int OutboundTimeout { get; set; }

        /// <summary>SSL 证书。</summary>
        internal X509Certificate SslCertificate { get; set; }

        /// <summary>SSL 域名。</summary>
        internal string SslDomain { get; set; }

        /// <summary>SSL 协议。</summary>
        internal SslProtocols SslProtocols { get; set; }

        /// <summary>同步 IO 操作。</summary>
        /// <remarks>默认值：True</remarks>
        internal bool SynchronousIO { get; set; }

        /// <summary>对响应体分块。</summary>
        /// <remarks>默认值：False</remarks>
        public bool Chunked { get; set; }

        /// <summary>限制请求大小。</summary>
        /// <remarks>默认值：1 GB。</remarks>
        public int MaxRequest { get; set; }

        /// <summary>允许压缩。</summary>
        public bool Compression { get; set; }

        #endregion

        #region launcher

        Thread _thread = null;

        /// <summary>启动服务器。</summary>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="InvalidOperationException"></exception>
        /// <exception cref="SocketException"></exception>
        public void Run(int port = 0, bool await = true)
        {
            if (port < 0 || port > 65535) throw new ArgumentOutOfRangeException(nameof(port));
            Run(new IPEndPoint(IPAddress.Any, port), await);
        }

        /// <summary>启动服务器。</summary>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="InvalidOperationException"></exception>
        /// <exception cref="SocketException"></exception>
        public void Run(IPEndPoint ipEndpoint, bool await = true)
        {
            if (_socket != null) throw new InvalidOperationException("存在已启动的 Socket。");
            if (ipEndpoint == null) throw new ArgumentNullException(nameof(ipEndpoint));
            IPEndPoint = ipEndpoint;

            var ipep = ipEndpoint;
            _socket = new Socket(ipep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            _socket.Bind(ipep);
            _socket.Listen(500);
            var inTimeout = InboundTimeout;
            var outTimeout = OutboundTimeout;
            if (inTimeout > 0) _socket.ReceiveTimeout = inTimeout;
            if (outTimeout > 0) _socket.SendTimeout = outTimeout;

            _thread = new Thread(Listen);
            _thread.IsBackground = true;
            _thread.Start();

            if (await) while (_socket != null) Thread.Sleep(300);
        }

        /// <summary>关闭服务器。</summary>
        public void Shutdown()
        {
            var socket = _socket;
            if (socket != null)
            {
                try { socket.Shutdown(SocketShutdown.Both); } catch { }
                try { socket.Close(1); } catch { }
                _socket = null;
            }
        }

        #endregion

        #region listener

        Socket _socket = null;

        int GetPort()
        {
            var socket = _socket;
            if (socket == null) return 0;
            try
            {
                if (socket.LocalEndPoint is IPEndPoint ipep)
                {
                    return ipep.Port;
                }
            }
            catch { }
            return 0;
        }

        void Listen()
        {
            if (SynchronousIO)
            {
                while (_socket != null)
                {
                    var socket = null as Socket;
                    try
                    {
                        socket = _socket.Accept();
                    }
                    catch
                    {
                        if (socket != null)
                        {
                            try { socket.Close(); } catch { }
                            try { socket.Disconnect(false); } catch { }
#if !NET20
                            try { socket.Dispose(); } catch { }
#endif
                        }
                        break;
                    }
                    if (socket != null) ThreadPool.QueueUserWorkItem(Process, socket);
                }
            }
            else
            {
                var e = new SocketAsyncEventArgs();
                e.UserToken = this;
                e.Completed += (sender, arg) => Process(arg);

                Socket dummy = null;
                Accept(e, ref dummy);
            }
        }

        void Accept(SocketAsyncEventArgs e, ref Socket socket)
        {
            e.AcceptSocket = null;
            var async = false;
            try
            {
                async = _socket.AcceptAsync(e);
            }
            catch
            {
                if (socket != null)
                {
                    try { socket.Close(); } catch { }
                    socket = null;
                }
                return;
            }
            if (!async) Process(e);
        }

        void Process(SocketAsyncEventArgs e)
        {
            var socket = null as Socket;
            if (e.SocketError == SocketError.Success) socket = e.AcceptSocket;

            Accept(e, ref socket);
            if (socket == null) return;
            Process(socket);
        }

        void Process(object obj)
        {
            var socket = obj as Socket;
            if (socket == null) return;

            try
            {
                var conn = null as MiniConnection;
                conn = new MiniConnection(this, socket);
                conn.BeginRead();
            }
            catch (Exception ex)
            {
                Shutdown();
                Logger.Web.Exception(ex, this);
                CatchException?.Invoke(ex);
                return;
            }
        }

        #endregion

    }

}
